Skip to content

Conversation

NickSdot
Copy link
Contributor

@NickSdot NickSdot commented Oct 1, 2025

This PR adds a new @maybe directive that conditionally renders HTML attributes with values, complementing the existing boolean attribute directives like @checked, @selected, @disabled, @required, and @readonly.

Problem

While we have directives for boolean attributes, we still need verbose @if ... @endif blocks for attributes with values:

<a href="#" @if($title) title="{{ $title }}" @endif>Link</a>

We cannot keep adding specific directives for every possible attribute, so we need a dynamic solution.

Solution

The @maybe directive renders an attribute with a value only when the value is not null, not an empty string, and not whitespace-only:

<a href="#" @maybe('title', $title)>Link</a>

Before/After

{{-- before --}}
<a href="{{ $link->route }}" @if($link->title) title="{{ $link->title }} @endif" @if($link->rel) rel="{{ $link->rel }} @endif">
    {{ $link->label }}
</a>


{{-- after --}}
<a href="{{ $link->route }}" @maybe('title', $link->title) @maybe('rel', $link->rel)>
    {{ $link->label }}
</a>

{{-- before --}}
<img src="{{ $image->url }}" @if($image->alt) alt="{{ $image->alt }}" @endif @if($image->caption) data-caption="{{ $image->caption }}" @endif />

{{-- after --}}
<img src="{{ $image->url }}" @maybe('alt', $image->alt) @maybe('data-caption', $image->caption) />

Behaviour Matrix

The directive intentionally differs from a simple @if() check by treating 0 and false as valid values, since these are common in data attributes for counts, flags, and boolean states.

Value Renders
'foo' data-attribute="foo"
0 data-attribute="0"
false data-attribute="false"
true data-attribute="true"
'' (nothing)
null (nothing)
' ' (nothing)

Naming

I considered several alternatives: @when (too generic, likely better for other future use cases), @flag (implies boolean values only, whereas this handles strings, numbers, bools), @attribute and @optional (too long), @attr and @set (don’t make the conditional nature clear).

@has was tempting as it reads well: “has $title, then render title”. However, the parameter order would need reversing to @has($title, 'title'), which breaks the pattern of other Blade directives where the static value comes first.

@opt is appealingly terse but perhaps too cryptic.

@maybe has the right balance. It’s short, clearly conditional, and reads naturally with the attribute name first: “maybe render title if $title”.

@shaedrich
Copy link
Contributor

To me, the naming is not intuitive. I would call it @flag() or the like

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 1, 2025

To me, the naming is not intuitive. I would call it @flag() or the like

@flag would work well if this was exclusively for boolean data attributes, but since it handles any attribute with any value type, @maybe, @when or @optional and obviously @attribute (both too long, IMO) are more accurate. A title="" or target="" attribute aren't flags, though. Also flag doesn't really make it clear that it is conditional.

I appreciate the feedback, but I'll leave the naming to Taylor.

@shaedrich
Copy link
Contributor

It'd be fine with @optional

@faissaloux
Copy link
Contributor

Good one! One problem is the naming, I think @attribute would be better.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 1, 2025

I updated the naming section of the PR description.

@shaedrich
Copy link
Contributor

Thanks a lot 👍🏻

@imacrayon
Copy link
Contributor

I think this should mirror @class, there’s precedent with that directive:

<a href="{{ $link->route }}" @attr(['title' => $link->title, 'rel' => $link->rel])>
    {{ $link->label }}
</a>

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 2, 2025

I think this should mirror @class, there’s precedent with that directive:


<a href="{{ $link->route }}" @attr(['title' => $link->title, 'rel' => $link->rel])>

    {{ $link->label }}

</a>

It is different in the sense that the @class directive always has at least two entries.

For this use case here it makes it longer by 6 chars for the majority of situations where we have one data attribute. Don't really like it because the reason for the PR is to make things less verbose.

That said, I'll have a look if we can support both.

Edit:
@imacrayon it's easy to support both, passing as initially proposed here and as array. I think it's important to keep the initally proposed syntax too, because it's shorter for the majority case. As the below example shows the array syntax only really is adding it's marginal value >1 entries.

Happy to implement. For now I'll leave it to Taylor to decide first.

{{-- Attributes: 1 --}}
<a @if($link->title) title="{{ $link->title }} @endif">
<a @maybe('title', $link->title)>
<a @maybe(['title' => $link->title])>

{{-- Attributes: 2 --}}
<a @if($link->title) title="{{ $link->title }} @endif" @if($link->rel) rel="{{ $link->rel }} @endif>
<a @maybe('title', $link->title) @maybe('rel', $link->rel)>
<a @maybe(['title' => $link->title, 'rel' => $link->rel])>

{{-- Attributes: 3 --}}
<a @if($link->title) title="{{ $link->title }} @endif" @if($link->rel) rel="{{ $link->rel }} @endif  @if($link->clickId) data-tracker="{{ $link->clickId }} @endif">
<a @maybe('title', $link->title) @maybe('rel', $link->rel) @maybe('data-tracker', $link->clickId)>
<a @maybe(['title' => $link->title, 'rel' => $link->rel, 'data-tracker' => $link->clickId])>

@shaedrich
Copy link
Contributor

fyi, previous unsuccessful attempt at @attributes: #52783

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 2, 2025

fyi, previous unsuccessful attempt at @attributes: #52783

Similar. Mine is minus the complexity. Though, apparently high demand for a solution.

@hctorres02
Copy link

I tried using $attributes->merge([ ... ]), but there's no way to apply a condition to data-* attributes. The filter must be done elsewhere.

This directive is quite valid. It could be expanded to include conditions like the @class directive, but implementation requires care.

@timacdonald
Copy link
Member

I haven't dug deep into how this renders attributes, but I would expect the following to happen for these different attribute types.

[
    'crossorigin',                            // crossorigin
    'data-persistent-across-pages' => 'YES',  // data-persistent-across-pages="YES"
    'remove-me' => false,                     // [removed]
    'keep-me' => true,                        // keep-me
    'null' => null,                           // [removed]
    'empty-string' => '',                     // empty-string=""
    'spaced-string' => '   ',                 // empty-string="   "
    'zero' => 0,                              // zero="0"
    'one' => 1,                               // zero="1"
];
<div
    crossorigin
    data-persistent-across-pages="YES"
    keep-me
    empty-string=""
    spaced-string="   "
    zero="0"
    one="1"
/>

This will keep it inline with how Vite handles attribute types and values.

@timacdonald
Copy link
Member

See arbitrary attributes in #43442 for more on these decisions.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 7, 2025

I haven't dug deep into how this renders attributes, but I would expect the following to happen for these different attribute types.

[

    'crossorigin',                            // crossorigin

    'data-persistent-across-pages' => 'YES',  // data-persistent-across-pages="YES"

    'remove-me' => false,                     // [removed]

    'keep-me' => true,                        // keep-me

    'null' => null,                           // [removed]

    'empty-string' => '',                     // empty-string=""

    'spaced-string' => '   ',                 // empty-string="   "

    'zero' => 0,                              // zero="0"

    'one' => 1,                               // zero="1"

];
<div

    crossorigin

    data-persistent-across-pages="YES"

    keep-me

    empty-string=""

    spaced-string="   "

    zero="0"

    one="1"

/>

This will keep it inline with how Vite handles attribute types and values.

Hey Tim! I already read that in your comment to the PR linked above. But I kindly disagree. Personally I don't care what Vite does, what I care about is how I can add my own attributes in a non-verbose way.

This PR seeks to handle the majority case we deal with every single day, not to be "aligned" with the @class directive, nor be "unified" with Vite. Because it doesn't make sense for many situations.

Differences:

  • It intentionally will render false, and true as strings.
  • It will not render space only strings.
  • It doesn't have value-less attributes at all, because it's shorter to simply add it in your HTML. There is no value in having a always renderable attribute in the array even.
  • It doesn't support array syntax at all; I showed above how array syntax does not really add value compared to multiple @maybe.

As I mentioned in my PR description, I would name this @maybe to keep @attributes for future use cases (perhaps like yours).

Both concepts are valid, but they cannot be merged in one (one wants to render false, one doesn't). Hence, what you are asking for is unfortunately nothing for this PR. 🙏

@timacdonald
Copy link
Member

Appreciate you pushing back, @NickSdot! Always appreciated.

To clarify, when I say Vite, I mean what we do in Laravel itself. Having different attribute rendering mechanics for two Laravel features seems like a footgun.

But even taking the stand that we don't want to be inline with Laravel's attribute handling in the Vite space, I would still push back on the current rendering proposal. If nothing else, we should respect HTML itself. It explicitly mentions that true and false are not valid for boolean attributes.

Screenshot 2025-10-07 at 14 07 00

@timacdonald
Copy link
Member

timacdonald commented Oct 7, 2025

I think my brain goes to tooling like jQuery when I see that we don't render empty strings and whitespace only strings. I probably need to think on that some more to come up with a compelling argument as my brain is deep in other stuff right now.

None of this is a hill I wanna die on, btw. Just sharing a perspective and prior art in Laravel related to the feature to ensure we keep the framework cohesive when and where it makes sense.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 7, 2025

Having different attribute rendering mechanics for two Laravel features seems like a footgun.

@timacdonald well, as I mentioned above, there is no way to satisfy two completely contrary concepts in one solution. Below I make a point why both are valid (+ spec conform) and why we need two mechanisms.

But even taking the stand that we don't want to be inline with Laravel's attribute handling in the Vite space, I would still push back on the current rendering proposal. If nothing else, we should respect HTML itself. It explicitly mentions that true and false are not valid for boolean attributes.

Screenshot 2025-10-07 at 14 07 00

I knew that this will be the next argument. :) For the follwing reasons I need to push back again:

1) There are more than one relevant spec here.

The ARIA spec for instance. Accessibility APIs expect explicit tokens. E.g:

aria-hidden="false"
aria-expanded="false"

2) Only valid for presence indicators (boolean attributes)

What you are quoting is a separate concept with explicit behaviour. This, however, does not mean that I cannot use true/false in enumerated attributes (next section after the one on your screenshot: 2.3.3 Keywords and enumerated attributes). True/False are not forbidden values for enumerated attributes. We can do whatever we want, just cannot expect "boolean attributes" behaviour from it.

To bring a simple example; you will agree that the folowing is neater

foo.active = foo.active === 'true' ? 'false' : 'true';

than

if (foo.hasAttribute('data-active')) {
  foo.removeAttribute('data-active');
} else {
  foo.setAttribute('data-active', '');
}

Enumerated attributes allow us excactly that. And here we are also back to the previous point: ARIA attributes are enumerated attributes, not boolean ones. Both ways are HTML spec conform, even if we ignore that ARIA is a separate spec.

3) Third Party Expectations

I am all for following specs. And I also have proven above that we are aligned with the spec. However, I still would like to throw in third party stuff. If a third party expects explicit true/false I cannot change that. You mentioned jQuery, JQuery Mobile expects it. The same is true for Bootstrap in many cases.

I think my brain goes to tooling like jQuery when I see that we don't render empty strings and whitespace only strings.

Yes, again, I don't object. Both concepts have their place. That's why we need two solutions for it. I believe we shouldn't try too hard to unify something that cannot be unified. It's like in the real world, we have a Slotted screwdriver and a Phillips screwdriver. Both aren't footguns, but solutions for different problems.

4) Last but not least
We are pretty focused on custom attributes right now. But please don't forget, I should totally be able to decide on my own if I want to have title="false" to show "false" in a little tooltip in a, for instance, in classifier interface. And I should be able to hide a title tooltip on something if, for whatever reason, the value in title is .

You get the point: this is not only for custom data attributes.


I probably need to think on that some more to come up with a compelling argument as my brain is deep in other stuff right now.

None of this is a hill I wanna die on, btw. Just sharing a perspective and prior art in Laravel related to the feature to ensure we keep the framework cohesive when and where it makes sense.

Unfortunately, it (subjectively) feels like Taylor tends to close PRs when you hop in with objections. So if your objections are not fully thougt out... it's demotivating to be closed just because. No offense, of course! ❤️

I hope my arguments above are compelling enough to "book a win" here.

@timacdonald
Copy link
Member

timacdonald commented Oct 7, 2025

Unfortunately, it (subjectively) feels like Taylor tends to close PRs when you hop in with objections. So if your objections are not fully thougt out... it's demotivating to be closed just because. No offense, of course! ❤️

No offense taken. It is my job to offer opinions here and there. Sorry if I've put you off here or on other PRs.

I can see utility in a feature of this shape. FWIW, I hope some form of this gets merged.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 7, 2025

Sorry if I've put you off here or on other PRs.

No, it's also not like that. Wasn't put off myself. So far you luckily been supportive to all my PRs. Sorry if I didn't express myself clear enough. 🙏

@timacdonald
Copy link
Member

Awesome!

@willrowe
Copy link
Contributor

willrowe commented Oct 8, 2025

I fully agree with @timacdonald and would rather see a single @attributes directive that consistently handles this in a way that aligns with how you would expect it to when coming from the JavaScript/Vue/Vite side of things. Having too many ways to do essentially the same thing, but in slightly different ways makes it more difficult to learn. This feels too tailored to how one person may like to do things as opposed to something more general, powerful, and predictable.

@rodrigopedra
Copy link
Contributor

Adding to @willrowe's comment, the true and false as strings case can be easily handled by a user on their codebase:

@attrs([
    'aria-hidden' => $hidden ? 'true' : 'false',
    'aria-expanded' => $expanded ? 'true' : 'false',
])

One can easily add a helper to their code base if wanted. Or just use json_encode:

@attr([
    // json_encode will output booleans, numbers and null as a unquoted strings
    'aria-hidden' => json_encode($hidden), 
    'aria-expanded' => json_encode($expanded),
])

But attribute toggling, as @timacdonald described, would be very awkward to accomplish with the current proposal.

Also, calling the directive @maybe is a nay from me. Intent is unclear and confusing.

Mind that Blade's directives can also be used for non-HTML content, like markdown emails and Envoy tasks.

I'd prefer, if added, for it to be called something like @attr(), or anything that closely resembles its intent.

If different behavior due to aria- and data- attributes is desirable, why can't we have both?

Insert "Why not both?" meme here

We could add a @attr directive that behaves like @timacdonald described and like our Vite plugin already does, and a @aria or @dataset directive that behaves like this PR is proposing.

A @aria or @dataset directive could even auto-prefix its attributes and behave however it is needed for those cases.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 8, 2025

JavaScript/Vue

This is Blade. Just saying.


Y'all keep discussing things that this PR doesn't seek to solve. As we now already know there must be two different solutions because both concepts are diametrical to each other. This isn't "tailored" to one persons requirements, this is the majority use case. We all set title and rel attributes all the time.

The alternative examples proposed above are hilarious. You realise that they are longer than writing the actual control flows this PR attempts to get rid off?

About naming, I repeat, I leave that to Taylor.

Guys, keep on bike shedding unrelated stuff instead of working on a complementing PR to add the other missing piece. I am sure that's how we will get good things! ✌️❤️

Edit:
Imagine having json_encode in your Blade files.

Edit 2:

If different behavior due to aria- and data- attributes is desirable, why can't we have both?

And then a @title, @rel, @target etc. directives, right?

@rodrigopedra
Copy link
Contributor

And then a @title, @rel, @target etc. directives, right?

Of course not.

Those would be covered in the behavior everyone would expect a @attr directive to behave.

With sane rendering rules that follow the HTML spec, minus aria- or data- attributes, which were later additions.

Imagine having json_encode in your Blade files.

Sure, mate. It is such an odd case it got a custom directive, a wrapper class, and a section on docs.

https://laravel.com/docs/12.x/blade#rendering-json

This isn't "tailored" to one persons requirements, this is the majority use case. We all set title and rel attributes all the time.

Yes, it is. Or at least when one prioritizes aria- and data- attributes rules over all other HTML attributes.

The gripe is not on the value of the directive, as I, and I am sure others who commented out, like the proposed shorthand syntax in general.

The gripe is on the proposed esoteric rendering rules.

This argument doesn't make any sense on the title or rel attributes, as they would be fine if the rendering rules followed the HTML spec, as proposed by many commenters, and what we also already have for our Vite plugin.

But whatever, you do you.

Good luck with your PR. I like the idea, just not the oddities, such as treating booleans as strings (which is perplexing).

If not merged, consider publishing it as a package. I am sure many other developers would benefit from it for aria- and data- attributes.

Have a nice day =)

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 8, 2025

I answered the HTML spec question above in detail. This is spec conform. Read up enumeration attributes instead of ignoring it and liking comments which also got it wrong.

Enumeration example from ARIA:
true; false; undefined

Enumeration example from app:
true; false; unsure

These have nothing to do with boolean attributes.

Unfortunately, I cannot do more to help you to understand the difference.

I am sure many other developers would benefit from it for aria- and data- attributes.

Thanks for making the argument for getting this merged. And yet you don't want to see it merged. Because it isn't tailored to your use case? ;)

I wish you the same!

@rodrigopedra
Copy link
Contributor

rodrigopedra commented Oct 8, 2025

And yet you don't want to see it merged.

I do want to see it merged. Just not with such esoteric rendering rules.

I find the syntax great:

<a href="..." @attr('title', $title)>{{ $label }}</a>

Where the title attribute is not rendered at all if $title is null or empty or false, and thus does not overshadow the <a> tag's content for a screen reader with an empty string.

The proposed syntax is very handy. Just not your particular use case for rendering attribute values in such a manner no one would expect.

Because it isn't tailored to your use case? ;)

Not my use case. HTML attributes spec.

Imagine someone using Web Components opening issue after issue as they expect boolean attributes to behave conforming to the spec.

Enumeration attributes are another spec suitable to specific use cases. It is not the general use case. And as such, IMO, it could be subject to a future addition, as I believe the general use case would benefit more developers. Or even provided by a 3rd-party package.

I am sorry. I won't spend more of my time trying to help you bring this addition to the framework.

I wish you the best luck.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 16, 2025

Booleans

I promise I am not stubborn for the sake of it. I, however, cannot at all see what the added value of the following would be:

@attr('contenteditable', a($isEditable))
or
@attr('contenteditable', $isEditable)

vs what I propose here

@if($isEditable) contenteditable @endif

Why would we do that? It doesn't safe anything. Value-less attributes, aka boolean attributes per HTML spec, aren't what this PR is trying to solve. Like, if I'd need an actual boolean attribute, I'd go with a normal @if. If I want to have something special, I'd reach for the special directive.

The whole promise of the directive I propose here is to pass values through to make verbosity go away. As in:

<a href="#" @if($title) title="{{ $title }}" @endif>Link</a>
vs
<a href="#" @maybe('title', $title)>Link</a>

There we actually move the needle. But now we are even talking about adding extra helpers which would make writing a simple attribute more awkward than it is now. Mind you, we only talk about this because we consider booleans important in this directive even though the directive would not make anything shorter or better for boolean attributes.

Opinion

I can think back and forth and take everything mentioned here honestly into account and still think this directive, as is, works sensible and how we mostly need it every day.

Naming

But, one concern is I don't see a great path forward to solving all use cases. For example if we were to call this @attr and then have an @attributes that behaves slightly differently that feels confusing / annoying to me. 😕

Well, that's ironically why @maybe (or any other name NOT close to "attribute") actually shines. You must look it up, you learn what it does and then you either love it or don't use it for the use case you have.

@taylorotwell, perhaps we could go one better and meme-ify this by calling it @lit (as in literal)? I am sure devs will find the directive "lit 🔥" because it will greatly reduce verbosity.

Or should we call it @nick to forever put the blame or fame on me? Joking. 🫠

Others

Do any other frameworks solve this issue cleanly? Or at least have a similar function / helper that behaves like this?

I cannot come up with one. Most go the verbose way as in foo={$value ? 'true' : 'false'}. But they are not as cool as Laravel, so we probably shouldn't compete with them. The closest to what I propose here is probably how Vue and Svelte render things.

Value Svelte (playground) Vue (playground) @maybe Same
true data-foo="true" data-foo="true" data-foo="true"
false data-foo="false" data-foo="false" data-foo="false"
null (nothing) (nothing) (nothing)
0 data-foo="0" data-foo="0" data-foo="0"
1 data-foo="1" data-foo="1" data-foo="1"
"foo" data-foo="foo" data-foo="foo" data-foo="foo"
'' data-foo data-foo="" (nothing)
' ' data-foo=" " data-foo=" " (nothing)

Vue & Svelte are cool, too. However, I don't like how they handle empty strings because it doesn't make sense for things like title or data-title. If someone argues a boolean attribute should not be present when unused, we can argue the same for a value holding attribute.

Edit: Both, Vue and Svelte, render the small subset of standard HTML attributes as per spec. The playground links doen't make that clear. However, this does not affect every non-standard boolean attributes which outnumber the standard attributes.

Holistic Alternative

But, one concern is I don't see a great path forward to solving all use cases. For example if we were to call this @attr and then have an @attributes that behaves slightly differently that feels confusing / annoying to me. 😕

I believe boolean attributes make this more complicated as it should be -- for literally zero value added. This directive is not made for actual boolean attribute behaviour, which is different from all other HTML attributes. So perhaps to have a @bool (no strong opinion about the name) directive to get them out of the discussion here is the real answer?

Looking at everything discussed, perhaps the following is what could cover everything?

Value @enum @data @aria @bool / @flag @attr aka @maybe
('foo', "bar") foo="bar" data-foo="bar" aria-foo="bar" foo foo="bar"
('foo', "0") foo="0" data-foo="0" aria-foo="0" (nothing) foo="0"
('foo', 0) foo="0" data-foo="0" aria-foo="0" (nothing) foo="0"
('foo', "1") foo="1" data-foo="1" aria-foo="1" foo foo="1"
('foo', 1) foo="1" data-foo="1" aria-foo="1" foo foo="1"
('foo', false) foo="false" data-foo="false" aria-foo="false" (nothing) foo="false"
('foo', true) foo="true" data-foo="true" aria-foo="true" foo foo="true"
('foo', '') foo="" data-foo="" aria-foo="" (nothing) (nothing)
('foo', ' ') foo="" data-foo="" aria-foo="" (nothing) (nothing)
('foo', null) (nothing) (nothing) (nothing) (nothing) (nothing)

The @data and @aria directives would behave exactly like @enum in the sense of rendering, but auto-prefix for convenience.

Enum and Bool is exactly what they are called in the HTML spec. But, again, no strong feelings about naming (anyway, please note how they all have 4 chars; banger!) from my side; but they should be short. I also have no strong feelings on how @enum and its aliases should work. But I feel strong about that @maybe (or whatever the name will be) should exist as I proposed it and that booleans should be treated as what they are: a special case.

Negation Operator Alternative

One more option is the following.

@attr('foo', true) -> foo="true"
@attr('!foo', true) -> foo

@attr('foo', false) -> foo="false"
@attr('!foo', false) -> nothing

This would be a special toggle for boolean value behaviour. We are used to the logical negation operator "!" in PHP. So, why not? It's a compromise with the least added chars. It takes everyones arguments into account and allows for both use case brought up here (if I didn't miss anything), without dismissing one side with "yours should be more verbose" or adding a new helper method. It also would help to not maintain an exceptions list.


cc'ing the people with concerns: @timacdonald @rodrigopedra @willrowe


Edits after feedback:

  • added @flag as alternative name for @bool
  • added note to Vue/Svelte rendering of standard boolean attribute subset
  • added "Negation Operator Alternative" section

@willrowe
Copy link
Contributor

Why would we do that? It doesn't safe anything. Value-less attributes, aka boolean attributes per HTML spec, aren't what this PR is trying to solve. Like, if I'd need an actual boolean attribute, I'd go with a normal @if. If I want to have something special, I'd reach for the special directive.

The main reason I want an @attributes directive, is to be able to cleanly define attributes on Blade components dynamically, similar to how the component attribute bag ($attributes) works inside of the components themselves. Right now, you cannot use @if/@endif on Blade components like this:

<x-component @if($condition) boolean-attribute @endif />

Because I want to use this with Blade components, I would like for it to handle the attribute values consistently with how the component attribute bag does, which I outlined previously.

@browner12
Copy link
Contributor

I've been avoiding hopping in this PR, but I think I have to at this point to voice my support for the comments from:

and against @NickSdot's position.

I think the original goal of this PR was to solely to solve a verbosity issue, which is great. But along the way we discovered other issues it can (and should) solve, like cleaner boolean attributes, and better Blade component support.

While the OP is correct, we can use simple @if directives in normal HTML for our boolean attributes, @willrowe correctly pointed out we cannot use them in Blade components.

<x-card
    @if($condition) disabled @endif
></x-card>

<x-card
    {{ $condition ? 'disabled' : '' }}
></x-card>

Both of these components will throw compilation errors. I have a related issue open at #57123. If @timacdonald's render matrix were used, and possibly some minor work were done on the component compiler, we could get this feature working for Blade components, allowing them to be more versatile and be used in situations they couldn't before.

I, for one, think we CAN have a single unified directive that solves all these problems, and I think the correct answer was already proposed by @rodrigopedra. For any of the special types:

  • true
  • false
  • null

we force the calling code to explicitly state it wants a string.

<div
    @attr('title', 'My Title')
    @attr('checked', true)
    @attr('disabled', false)
    @attr('selected', null)
    @attr('data-boolean', true)
    @attr('data-boolean-string', true ? 'true' : '')
    @attr('data-empty-string', '')
    @attr('data-dont-show-empty-string', '' ?: false)
    @attr('data-whitespace-string', '  ')
    @attr('data-dont-show-whitespace-string, trim(' ') ? ' ' : false)
    @attr('aria-hidden', true ? 'true' : false)
    @attr('minlength', 0)
    @attr('maxlength', 1)
></div>
<div
    title="My Title"
    <!--disabled-->
    checked
    <!--selected-->
    data-boolean
    data-boolean-string="true"
    data-empty-string=""
    <!--data-dont-show-empty-string-->
    data-whitespace-string="  "
    <!--data-dont-show-whitespace-string-->
    aria-hidden="true"
    minlength="0"
    maxlength="1"
></div>

The beauty of the render matrix proposed by @timacdonald and the suggestion by @rodrigopedra is it gives the calling code FULL control over how the actual HTML is rendered, and allows us to correctly render both boolean, enumerated, and string attributes 100% correctly according to spec.

The tradeoff is slightly more verbose code when we have to deal with these special types, but I think the versatility we gain is definitely worth it.

@rodrigopedra
Copy link
Contributor

rodrigopedra commented Oct 16, 2025

@NickSdot cc'ed me, and I understood it as a request for comments.

So, for those who are willing to read it all, please forgive me in advance for the lengthy response.

boolean attributes per HTML spec, aren't what this PR is trying to solve

It is already clear the original intent is not to address boolean attributes.

But at this point I'd assume it would be clear a PR comment section is a place
of discussion where changes can be proposed to be a better fit to the overall
Laravel community, considering maintainability and use cases, an OP might
not have considered when they sent a PR.

Regarding enumerated attributes, the HTML spec states:

Some attributes, called enumerated attributes, take on a finite set of states.

The key here is the "some attributes", which implies they are a finite set.

Reference: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#enumerated-attribute

In other words, other than ARIA attributes, which follow their own spec, the HTML spec
notes each enumerated attribute along with their allowed values.

For example, the contenteditable attribute has this note on the spec:

The contenteditable content attribute is an enumerated attribute with the following keywords and states:

Keyword State Brief description
true True The element is editable.
false False The element is not editable.
plaintext-only Plaintext-Only Only the element's raw text content is editable; rich formatting is disabled.

The attribute's missing value default and invalid value default are both the Inherit state. The inherit state indicates that the element is editable (or not) based on the parent element's state. The attribute's empty value default is the True state.

Reference: https://html.spec.whatwg.org/#contenteditable

I picked contenteditable as it is among the only four enumerated attributes defined
on the HTML spec to take true and false as possible values. The other enumerated attributes doing so are draggable, spellcheck, and writingsuggestions.

Handling the "output a boolean as a string" case by default to address just 4 cases listed in the spec seems to me an overkill.

Especially when for all of them the "missing value default state" is true, in other words, contenteditable and contenteditable="true" mean the same.

Also, there are 33 boolean attributes in the specs, and it would be unfeasible and impractical to add a custom directive to each one, versus only 4 enumerated attributes that expect a "boolean as a string" among their valid values.

Reference: https://html.spec.whatwg.org/multipage/indices.html#attributes-3

When using ARIA attributes, I don't think it would be a hassle to either use a helper to stringify a boolean (either a custom one or json_encode) or to use the ternary operator to stringify it in place.

MDN also has a glossary entry on enumerated attributes with two interesting notes:

In computer science, an enumerated type is a data type consisting of a limited set of named values.

and

In HTML, enumerated attributes are attributes with a limited, predefined set of text values. For example, the global HTML dir attribute has three valid values: ltr, rtl, and auto.

References:

Enumerated attributes, much like enums in most languages, have a previously known set of values. Also, the HTML spec does not make a provision for user-defined custom enumerated attributes.

As noted by the MDN reference and also on the HTML spec, the DOM treats known enumerated attributes according to their definition on the specs. Including reflection when using the DOM.

In contrast, a user can define custom web elements and register them to the DOM.

I am not aware of a mechanism to register custom user-defined enumerated attributes. Unlike custom elements, I couldn't find a way to tell the DOM of them.

Of course one can, and should be able to, use true and false as strings for data- attributes or custom user-defined attributes, regardless of how the DOM will treat these attribute values, for tools like Livewire or custom web elements that are defined on userland.

Mind that the spec does not provision true or false, nor numbers having special treatment on custom data attributes (a.k.a. data-* attributes), as they are not enumerated attributes, since their valid values are unknown.

Actually the spec states this about custom data attributes:

Custom data attributes are intended to store custom data, state, annotations, and similar, private to the page or application, for which there are no more appropriate attributes or elements.

These attributes are not intended for use by software that is not known to the administrators of the site that uses the attributes.

Within an example block:

It would be inappropriate, however, for the user to use generic software not associated with that music site to search for tracks of a certain length by looking at this data.

This is because these attributes are intended for use by the site's own scripts, and are not a generic extension mechanism for publicly-usable metadata.

And later

User agents must not derive any implementation behavior from these attributes or values. Specifications intended for user agents must not define these attributes to have any meaningful values.

In other words, it is up to the developer to parse the value of these attributes from strings to any meaningful values.

So it makes little sense, at least to me, to handle the "booleans as strings" case before handling boolean attributes.

Reference: https://html.spec.whatwg.org/multipage/dom.html#custom-data-attribute

That is why I am suggesting the directive to treat boolean attributes first and add a helper to handle the cases where a user would want the values to be stringified.

The helper could also be useful when a user needs to stringify a boolean value in other places, such as JSON Resource:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->resource->id,
            'title' => $this->resource->title,
            'contents' => $this->resource->contents,
            
            // the helper name is open for suggestions
            'is_archived' => stringify($this->resource->is_archived),
        ];
    }
}

Moving forward, using your very example arguing that you want a streamlined way to do things:

<a href="#" @if($title) title="{{ $title }}" @endif>Link</a>
vs
<a href="#" @maybe('title', $title)>Link</a>

If the value of the $title variable is false, or any other falsy value, I would expect the new directive to behave just like the @if() ... @endif example you provided would behave. And not to output title="false".

The value of the $title variable could be fetched from a DB, from an external API, or from user-provided input, and I would still prefer them to be handled as boolean attributes first, as proposed by some comments that only aim to enrich the PR goals.

Another argument I would like to address is:

To bring a simple example; you will agree that the folowing is neater

foo.active = foo.active === 'true' ? 'false' : 'true';

than

if (foo.hasAttribute('data-active')) {
  foo.removeAttribute('data-active');
} else {
  foo.setAttribute('data-active', '');
}

I would say that using this is even neater:

foo.toggleAttribute('data-active');

As it uses a widely available feature across browsers. And does not depend on
a custom server-generated value that can change regardless of the frontend code,
and that has no special meaning according to the specs.

Reference: https://developer.mozilla.org/en-US/docs/Web/API/Element/toggleAttribute

It is clear you are frustrated by the suggestions that diverge from your initial proposal.

And as you agree, there won't be a one-size-fits-all solution.

My suggestion to add both a directive and a helper was to try to accommodate a solution that works for both cases concerned in the comments section of this PR.

Mind that future cases can be brought up by users on issues if this PR is merged, and that the maintainers would need to decide on how to accommodate valid use cases without breaking what is being discussed here.

I guess we could consider these solutions:

  1. Add two directives:
  • One for boolean attributes to cover the expectations raised from me and others regarding default behavior and maintainability
  • Other for enumerated attributes, ARIA attributes and custom user-defined attributes that could expect booleans as strings depending on the user's frontend implementation
  1. Add a directive that handles boolean attributes first and a helper to stringify values when needed.
  • The helper could be used in other places, such as JSON Resources
  1. Add three directives: @attr(), @aria(), @data(), where:
  • @attr() would behave as the @if..@endif examples, and won't make any assumptions on the values provided, as such it would treat falsy values as booleans attributes, good to cover most usages, and to cover those among the 33 boolean attributes that don't have dedicated directives
  • @aria() that would auto-prefix the attribute, and stringify booleans as it is a common idiom for many ARIA attributes on the ARIA spec
  • @data() that would auto-prefix the attribute, and I would leave the behavior (boolean attribute/stringified booleans) to be decided by the maintainers

Again, I like the addition of the new directive, and I don't have any personal gripes with anyone involved in the discussion.

My comments are aimed to enrich the proposal and bring the best value both for the users and maintainers.

@browner12
Copy link
Contributor

@rodrigopedra while I agree with the overall sentiment of the previous comment, I also don't think we need to compromise with multiple directives and a helper. I think the single directive (@attr or @attribute or @maybe or whatever) plus Tim's proposed render matrix plus your solution for the special cases gives us both a concise and flexible API for this feature.

Would love to see any examples from people where they think this approach either wouldn't work completely, or would be less than ideal before we start complicating the solution.

@rodrigopedra
Copy link
Contributor

@browner12 I agree.

At first I had solution no. 2 as the first one, but at the end I changed orders to try to accommodate a middle-ground solution.

My preference is for a single directive that handles falsy values as they would be handled when using the @if..@endif idioms.

The only 4 enumerated attributes that take true and false as enumerated values (contenteditable, draggable, spellcheck, and writingsuggestions) are also very unlikely to need their value to be dynamically rendered. Usually setting spellcheck="false" is done on a per-needed basis (for example, in login forms) and remains static.

It would also cover the 33 boolean attributes in the spec that don't already have dedicated directives, and nor should, but could benefit from the new directive.

For example, the muted and controls attributes on the audio and video tags are more likely to be configured dynamically by an end user preference.

Also, as custom data attributes are meant to be used by the site's administrator, per the spec, one could also expect them to behave as boolean attributes, just checking for their presence.

Boolean attributes on user-defined attributes are as a special case, as are using true and false as strings on user-defined attributes. It depends on the project's requirements and on how their front end is coded.

For these custom data attributes, or ARIA attributes, which a user could want to use true and false as strings, they can use a custom helper, or json_encode or a ternary operator in place.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 16, 2025

<x-card @if($condition) disabled @endif></x-card>

Since when are we mixing x-component syntax with @-syntax?

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 16, 2025

@rodrigopedra

What's your response to that, for example, Vue and Svelte rendering regarding booleans is exactly how I propose it here?

@browner12
Copy link
Contributor

If I'm understanding your question correctly, we've been mixing directives into Blade components for at least 3 years. You can see here in the blame:

https://github.com/laravel/framework/blame/12.x/src/Illuminate/View/Compilers/ComponentTagCompiler.php

we make special accommodations for some directives, and also specifically for {{ $attributes }}.

FYI, you don't need to "Quote Reply" an entire comment. Makes the whole chain a little harder to read. We can usually pick up on context, and will ask for clarification if needed.

@rodrigopedra
Copy link
Contributor

What's your response to that, for example, Vue and Svelte rendering regarding booleans is exactly how I propose it here?

@NickSdot I was typing this response when your comment appeared.


I was curious to verify how the aforementioned Svelte and Vue playgrounds would handle boolean attributes. As @NickSdot just tested for a custom data-* attribute.

Links:

The results are:

Value Svelte Vue data- value boolean attribute
true data-foo="true" inert="" data-foo="true" inert="" stringified present
false data-foo="false" data-foo="false" stringified missing
null missing missing
0 data-foo="0" data-foo="0" stringified missing
1 data-foo="1" inert="" data-foo="1" inert="" stringified present
bar data-foo="bar" inert="" data-foo="bar" inert="" stringified present
'' data-foo="" data-foo="" inert="" stringified missing in Svelte, present in Vue
' ' data-foo=" " inert="" data-foo=" " inert="" stringified present

There is a difference in the Svelte results for the data-* attribute when an empty string is used from the results by @NickSdot.

Maybe it was an oversight, or they had an update in the meanwhile. But that doesn't change the overall results.

The gist is that boolean attributes are handled by both parsers based on the value's falsiness, except on the empty string, where the Vue and Svelte compilers disagree.

And the user-defined data-* attributes are stringified, which makes sense to not make any assumptions, as per the HTML spec these attributes "are not intended for use by software that is not known to the administrators of the site that uses the attributes."

@browner12
Copy link
Contributor

browner12 commented Oct 16, 2025

Vue does not appear to be rendering exactly as you proposed. Vue appears to be doing additional logic under the hood so it does correctly handle boolean HTML attributes by completely omitting the attribute when the value is false.

https://vuejs.org/guide/essentials/template-syntax#boolean-attributes

This is something that we could do, but the extra maintenance of trying to stay up to date with the HTML spec does not seem worth it.

@shaedrich
Copy link
Contributor

Fun fact: What initially brought me into this PR and made me suggest @flag() as naming was that I was thinking about a new, similar Blade directive with said name:

Blade::directive('datetime', function (string $varName) {
  return '<?php echo $__data[' . $varName . '] ? ' . $varName . ' : null; ?>';
});
// …
view('my-view', ['foo' => 'bar', 'disabled' => true]);
<button @flag('disabled')>Click me, {{ $foo }}</button>

@shaedrich
Copy link
Contributor

For context, almost 250,000 individual devs have installed the Laravel VS Code extension. We have 10 devs represented here with positive upvotes, and multiple people confused. That's a microscopic sample size. Now multiply that across the ecosystem.

Well, that is not a very representative metric in itself and rather distorting one actually. Just a small fraction of the community actively engages in discussions, so staying away from discussions can have a thousand different reasons than disagreement. On the other side, we have highly experienced community members like @timacdonald and @rodrigopedra who probably know the framework inside out. Not that that makes any of us better or right about this, more to disprove your point.

I also don't think we need to compromise with multiple directives and a helper.

One alternative to addressing both not having to have multiple directives as well as supporting multiple different notations might be the following:

enum BladeAttributeSpec
{
    case Html;
    case WaiAria;
    case Data;
    case Js;
    // …
}

Blade::directive('attr', function (string $name, mixed $value, BladeAttributeSpec $spec) {
	$attrRenderer = Blade::getAttributeRenderer($spec);
    return $attrRenderer->render($name, $value);
});

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 16, 2025

@rodrigopedra @browner12

Vue does not appear to be rendering exactly as you proposed. Vue appears to be doing additional logic under the hood so it does correctly handle boolean HTML attributes by completely omitting the attribute when the value is false.

This is only correct if we talk about the subset of standard boolean attributes. :foo="item.value" still renders to foo="false".

FYI, you don't need to "Quote Reply" an entire comment. Makes the whole chain a little harder to read. We can usually pick up on context, and will ask for clarification if needed.

You are right. I was still on my phone. Corrected.

This is something that we could do, but the extra maintenance of trying to stay up to date with the HTML spec does not seem worth it.

Well, it's not that the spec rapidly changes. We could do that of course. But note, this also doesn't account for non-standard boolean attributes.

There is a difference in the Svelte results for the data-* attribute when an empty string is used from the results by @NickSdot. Maybe it was an oversight, or they had an update in the meanwhile. But that doesn't change the overall results.

Also only correct for the subset of standard attributes. foo={item.value} still renders to foo.


So yeah, if this is the concern you two have, we could make exceptions for the few standard attributes to align with the behaviour above.

But let me add one more proposal. How about the following?

@attr('foo', true) -> foo="true"
@attr('!foo', true) -> foo

@attr('foo', false) -> foo="false"
@attr('!foo', false) -> nothing

This would be a special toggle for boolean value behaviour. We are used to the logical negation operator ! in PHP. So, why not? It's a compromise with the least added chars. It takes everyones arguments into account and allows for both use case brought up here (if I didn't miss anything), without dismissing one side with "yours should be more verbose" or adding a new helper method. It also would help to not maintain an exceptions list.

@NickSdot
Copy link
Contributor Author

Fun fact: What initially brought me into this PR and made me suggest @flag() as naming was that I was thinking about a new, similar Blade directive with said name:

I forgot that you brought up flag. If it would be decided to have a multi-directive approach as I suggested above, @flag instead of @boolcould also be a great fit (will add it to my above comment).

@browner12
Copy link
Contributor

@NickSdot can you add an example or 2 of where you think our proposal would either not work or be less than ideal?

@NickSdot
Copy link
Contributor Author

@NickSdot can you add an example or 2 of where you think our proposal would either not work or be less than ideal?

Sure. But what proposal? You mean this?

@attr('data-boolean-string', true ? 'true' : '')

It's not ideal because it defeats the goal of the PR. Making things less verbose by auto-passing.

@attr('data-boolean-string', $value ? 'true' : 'false')
vs
data-boolean-string="{{ $value ? "true" : "false" }}
@attr('data-boolean-string', $value) 

What I propose is not only shorter by the amount of chars we see here, but by the actual condition which the directive I propose takes care of internally with it's sensible defaults I described in the matrix in the PR description.

All three of you ask to add at least 18 characters to every single enum attribute that needs true/false, to make the actual boolean attributes case (which this directive isn't made for; we have boolean directives and @if) exactlty 2 (two) chars shorter. That just doesn't make sense. And I think everyone discussing in good faith at least should acknowledge this.

@if($value) data-boolean-string @endif
@attr('data-boolean-string', $value)

Could you add an example or 2 of where you think all 3 of my proposals would either not work or be less than ideal?

@NickSdot
Copy link
Contributor Author

Why would we do that? It doesn't safe anything. Value-less attributes, aka boolean attributes per HTML spec, aren't what this PR is trying to solve. Like, if I'd need an actual boolean attribute, I'd go with a normal @if. If I want to have something special, I'd reach for the special directive.

The main reason I want an @attributes directive, is to be able to cleanly define attributes on Blade components dynamically, similar to how the component attribute bag ($attributes) works inside of the components themselves. Right now, you cannot use @if/@endif on Blade components like this:

<x-component @if($condition) boolean-attribute @endif />

Because I want to use this with Blade components, I would like for it to handle the attribute values consistently with how the component attribute bag does, which I outlined previously.

This is literally a solved problem in Blade components as I mentioned here. And it has nothing to do with this PR.

image

The same is true for non-boolean attributes in Blade components btw. So, really not sure how it is related to this PR and also not sure why you would want to nullify the problem this very PR tries to solve.

cc @browner12 who came here for the same reason.

@browner12
Copy link
Contributor

Thanks for sharing an example. So to be clear, your position is our proposal doesn't make anything impossible, but makes it less than ideal because it makes things more verbose in a subset of the use cases.

Yes I have some examples, thanks for asking. There are 4 values that we disagree on the rendering of:

value yours ours
true attribute="true" attribute
false attribute="false"
'' attribute=""
' ' attribute=" "

Given the following examples:

<div
    @attr('disabled', false)
    @attr('disabled, true)
    @attr('data-custom', '')
    @attr('data-custom', '  ')
></div>

and your generated output:

<div
    disabled="false"
    disabled="true"
    <!-- missing data-custom=""-->
    <!-- missing data-custom="  "-->
></div>

your proposal would make generating valid HTML boolean attributes and empty string values impossible with this directive.

I know you have suggested the @if @else solution for boolean attributes, but myself, along with 3 other commenters believe they should be part of this directive.


So I think if we boil it down, the trade off is as follows:

Proposal Pro Con
yours uses fewer characters makes certain valid HTML attributes impossible
ours allows all valid HTML attributes more verbose in a subset of cases

I hope this is a succinct and thorough summary of both opinions.

Given these tradeoffs, I am of the opinion that I will sacrifice fewer characters in the following cases:

  • 4 attributes that accept true/false in their enumerations
  • aria attributes
  • data attributes

for the ability for this directive to be able to generate all valid HTML attributes. I believe this gives us a good base.

Automatic handling of the special cases to decrease the verbosity could be added either via automatic detection (increased maintenance) or a 3rd optional parameter.

@NickSdot
Copy link
Contributor Author

NickSdot commented Oct 17, 2025

Thanks for sharing an example. So to be clear, your position is our proposal doesn't make anything impossible, but makes it less than ideal because it makes things more verbose in a subset of the use cases.

It literally defeats the purpose of this PR. It can be straight closed then.

I know you have suggested the @if @else solution for boolean attributes, but myself, along with 3 other commenters believe they should be part of this directive.

No. I also proposed a @bool directive. You didn't bring any argument why that would be a bad idea.

So I think if we boil it down, the trade off is as follows:

Proposal Pro Con
yours uses fewer characters makes certain valid HTML attributes impossible
ours allows all valid HTML attributes more verbose in a subset of cases
I hope this is a succinct and thorough summary of both opinions.

No. Not in a "subset" of cases. The actual boolean arttibutes are the subset; so subset that they have their very own spec.

Given these tradeoffs, I am of the opinion that I will sacrifice fewer characters in the following cases:

  • 4 attributes that accept true/false in their enumerations
  • aria attributes
  • data attributes

for the ability for this directive to be able to generate all valid HTML attributes. I believe this gives us a good base.

Well, me and 14 others are of different opinion.
But we are totally fine with that you get what you want. Just not in this very directive.
I even offered to work on it allthough I don't need it myself.
Yet, my requirements are dismissed in favour of yours -- no matter how many arguments and good-will proposal adjustments I made in the discussion.

Automatic handling of the special cases to decrease the verbosity could be added either via automatic detection (increased maintenance) or a 3rd optional parameter.

No. That's not the only solution. I proposed:

  1. Multiple directives (can include one exactly as you want)
  2. Logical Negation Operator

You conveniently ignored both, without any acknowledgement or argument why either is bad. You also ignored my hint that "automatic detecion" would only apply to standard HTML attributes.

Personally, I think A) a third parameter is less nice than "!". B) Taylor doesn't like boolean flags in methods; why would it here be better than having multiple directives.

I don't understand why all three of you solely make arguments in an "either this or that directive" way. We can have both. Or three. Or four. No one of you made an argument why what I propose here should not exist apart from that you have different requirements to how it should work. So, let what you want to have be another directive. I am cool with that since I opened the PR.

So again, why can't this directive -- that covers the absolute majority of standard cases -- AND one that does what you propose exist?

Here, again a slimmed down version of what I posted above to make it super clear for you:

Value @whatever @maybe
('foo', "bar") whatever you want foo="bar"
('foo', "0") whatever you want foo="0"
('foo', 0) whatever you want foo="0"
('foo', "1") whatever you want foo="1"
('foo', 1) whatever you want 'foo="1"`
('foo', false) whatever you want foo="false"
('foo', true) whatever you want foo="true"
('foo', '') whatever you want (nothing)
('foo', ' ') whatever you want (nothing)
('foo', null) whatever you want (nothing)

As you see I don't even care about @data or @aria because I simply could use my beloved @maybe for it. I only added data/aria because others asked for it. And when I added it out of consideration for others, I explicitely mentioned that I personally do not care how @enum, @data and @aria would work; or be named. Quote:

no strong feelings about naming (anyway, please note how they all have 4 chars; banger!) from my side; but they should be short. I also have no strong feelings on how @enum and its aliases should work.

So, could we now please acknowledge that me and 15 others have a demand for this very directive I propose here and that hence the answer cannot be that we get in what you want but skip what we want?

I propose the following next steps:

  1. Each of you who wants something different either adjusts or removes @enum, @data, @aria, @bool/@flag from the below table and posts it in a comment.
  2. Y'all find a consent on how it should work.
  3. We find a name for @maybe that is acceptable, but leave the functionality as is.
  4. We get it done.
Table as Markdown


| Value            | `@enum`       | `@data`            | `@aria`            | `@bool` / `@flag`  | `@attr` aka `@maybe` |
|------------------|---------------|--------------------|--------------------|-------------|----------------------|
| `('foo', "bar")` | `foo="bar"`   | `data-foo="bar"`   | `aria-foo="bar"`   | `foo`       | `foo="bar"`          |
| `('foo', "0")`   | `foo="0"`     | `data-foo="0"`     | `aria-foo="0"`     | _(nothing)_ | `foo="0"`            |
| `('foo', 0)`     | `foo="0"`     | `data-foo="0"`     | `aria-foo="0"`     | _(nothing)_ | `foo="0"`            |
| `('foo', "1")`   | `foo="1"`     | `data-foo="1"`     | `aria-foo="1"`     | `foo`       | `foo="1"`            |
| `('foo', 1)`     | `foo="1"`     | `data-foo="1"`     | `aria-foo="1"`     | `foo`       | `foo="1"`            |
| `('foo', false)` | `foo="false"` | `data-foo="false"` | `aria-foo="false"` | _(nothing)_ | `foo="false"`        |
| `('foo', true)`  | `foo="true"`  | `data-foo="true"`  | `aria-foo="true"`  | `foo`       | `foo="true"`         |
| `('foo', '')`    | `foo=""`      | `data-foo=""`      | `aria-foo=""`      | _(nothing)_ | _(nothing)_          |
| `('foo', '   ')` | `foo=""`      | `data-foo=""`      | `aria-foo=""`      | _(nothing)_ | _(nothing)_          |
| `('foo', null)`  | _(nothing)_   | _(nothing)_        | _(nothing)_        | _(nothing)_ | _(nothing)_          |

How does that sound?

@Rizky92
Copy link
Contributor

Rizky92 commented Oct 17, 2025

HTML spec is too broad.
I think this needs to split, for attributes like disabled, checked, and the like, should be its own type of attribute.

While others can be functionally like @maybe('data-columntype', $isNumeric, 'integer', 'string').

@NickSdot
Copy link
Contributor Author

HTML spec is too broad. I think this needs to split, for attributes like disabled, checked, and the like, should be its own type of attribute.

Agree on this.

While others can be functionally like @maybe('data-columntype', $isNumeric, 'integer', 'string').

This defeats the goal of the PR because it would too long. Multiple directives solve that.

@browner12
Copy link
Contributor

It's not about what either 4 or 15 people want. It's about what's best and most maintainable for the framework as a whole. I'm making my argument for what I think is best, and you make your argument for what you think is best.

Then the BDFL comes in, passes judgement, and we all move on with our lives.

Of course 2 directives could exist. Or 3, or 4, or 10. But that adds a maintenance cost. If we choose to pay it, great. I've made my case for a single directive I believe best covers the use cases.

@shaedrich
Copy link
Contributor

But let me add one more proposal. How about the following?

@attr('foo', true) -> foo="true"
@attr('!foo', true) -> foo

@attr('foo', false) -> foo="false"
@attr('!foo', false) -> nothing

Now, that looks smart 😲 Very intriguing solution 🤔

I'm usually not in favor of multiple information within one string, but on one hand, it's pretty common in the framework already, and on the other, it's both quite short and addresses some problems from this discussion fairly well 👍🏻

@riyuk
Copy link
Contributor

riyuk commented Oct 17, 2025

Now, that looks smart 😲 Very intriguing solution 🤔

About the ! syntax: (as we all know) Tailwind uses ! for important-styles too. Though I'd flip it around - make @attr('foo', true) -> foo the default, and @attr('!foo', true) -> foo="true" for when you explicitly want the verbose output. Makes more sense to me since the ! would mean "verbose mode" instead of trying to follow spec behavior.

Actually strongly disagree at least as far as the current discussion goes. There are a tiny amount of people involved - and yet we have substantial disagreement and confusion about how this should even work.

I disagree with this take. From what I can see, the last PR that generated this level of discussion was at least 6 years ago. This PR is already in the top 10 most-commented PRs in the entire framework. That tells me the feature itself is valuable and people actually care about it.

@shaedrich
Copy link
Contributor

Now, that looks smart 😲 Very intriguing solution 🤔

About the ! syntax: (as we all know) Tailwind uses ! for important-styles too. Though I'd flip it around - make @attr('foo', true) -> foo the default, and @attr('!foo', true) -> foo="true" for when you explicitly want the verbose output. Makes more sense to me since the ! would mean "verbose mode" instead of trying to follow spec behavior.

I'd be totally down to that—good point 👍🏻

Actually strongly disagree at least as far as the current discussion goes. There are a tiny amount of people involved - and yet we have substantial disagreement and confusion about how this should even work.

I disagree with this take. From what I can see, the last PR that generated this level of discussion was at least 6 years ago. This PR is already in the top 10 most-commented PRs in the entire framework. That tells me the feature itself is valuable and people actually care about it.

And it's a discussion needed to make a PR result in well-implemented features 👍🏻

@NickSdot
Copy link
Contributor Author

@riyuk @shaedrich

Personally, I still believe the boolean case as the opposition here wants it is the minority; we use it less. I'd keep it as is and don't invert. However, I'd not die from it being inverted. If it would be decided to invert, I'd to say this would be more nice:

@attr('foo=', true) -> foo="true"
@attr('foo', true) -> foo

@attr('foo=', false) -> foo="false"
@attr('foo', false) -> nothing

Basically it says, if the = is passed, appending ='val' is forced.

@shaedrich
Copy link
Contributor

Makes sense 👍🏻

@AhmedAlaa4611
Copy link
Contributor

<x-card @if($condition) disabled @endif></x-card>

Since when are we mixing x-component syntax with @-syntax?

Since we start complicating things. 😂😂😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.